package org.vectrics.calendar.struts;

import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.ecs.html.Div;
import org.apache.ecs.html.Font;
import org.apache.ecs.html.IMG;
import org.apache.ecs.html.Script;
import org.vectrics.NotSupportedException;
import org.vectrics.SystemException;
import org.vectrics.calendar.CalendarItemDisplayInfo;
import org.vectrics.calendar.CalendarItemsDisplayInfo;
import org.vectrics.common.db.Expirable;
import org.vectrics.common.db.ExpirableRecordObject;
import org.vectrics.common.dom.DomUtils;
import org.vectrics.common.html.Cell;
import org.vectrics.common.html.Row;
import org.vectrics.common.html.Table;
import org.vectrics.common.html.TextHelper;
import org.vectrics.common.util.DateUtil;
import org.vectrics.provider.CoreServiceLocator;
import org.vectrics.struts.VectricsTagSupport;
import org.vectrics.user.Person;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

public abstract class CalendarTag extends VectricsTagSupport {

	private static final long serialVersionUID = 7010743813712387659L;
	
	public static String TIME_MODE_DAY = "day";
	public static String TIME_MODE_WEEK = "week";
	public static String TIME_MODE_MONTH = "month";
	
	
	public static String DETAIL_ALIGN_RIGHT = "RIGHT";
	public static String DETAIL_ALIGN_TOP = "TOP";
	public static String DETAIL_ALIGN_BOTTOM = "BOTTOM";

	private int initScriptIndex = 0;

	private String personId = null;
	private String timeMode = TIME_MODE_DAY;
	private String personBackgroundColor = "#eeeecc";
	private String meetingBackgroundColor = "#ccddee";
	private String backgroundColor = "#bbbbbb";
	private String gridLineColor = "#555555";
	private int minimumPersonWidth = 150;
	private int maximumPersonWidth = 350;
	private int maximumWidth = 500;
	private long minutesPerPixel = 2;
	private int timeMarginWidth = 80;
	private long personHorizSpacing = 5;
	private String detailAlign = DETAIL_ALIGN_RIGHT;
	public static String STRATEGY_VERTICLE = "verticle";
	public static String STRATEGY_HORIZONTAL = "verticle";
	private String renderingStrategyCode = STRATEGY_VERTICLE;
	private CalendarRenderingStrategy renderingStrategy = null;
	
	protected abstract Date getDateStart();
	protected abstract Date getDateEnd();
	protected abstract List getPeople();
	protected abstract List findPersonExpirables(Person person, Date dateStart, Date endDate);
	protected abstract void findDisplayAttributes(Person person, ExpirableRecordObject personExpirable, PersonExpirableDisplayAttributes displayInfo);
	protected abstract void personExpirableToXml(ExpirableRecordObject personExpirable, Element element, Locale locale);
	protected abstract String createJScript_ShowCalendarItemDetailsMethod();
	protected abstract String createJScript_DeactivateDetailDisplay();
	protected abstract String createJScript_OnItemClick();
	protected abstract String createJScript_OnCalendarClick(Person person);


	@Override
	protected String makeStartTag() {
		if (this.renderingStrategyCode.equals(STRATEGY_VERTICLE)) {
			this.renderingStrategy = new VerticleCalendarRenderStrategy();
		} else if (this.renderingStrategyCode.equals(STRATEGY_HORIZONTAL)) {
			this.renderingStrategy = new HorizontalCalendarRenderStrategy();
		} else {
			throw new SystemException("Do not know how to handle strategy: " + this.renderingStrategy);
		}
		Collection<Expirable> allPeoplesExpirables = new HashSet<Expirable>();
		log.debug("makeStartTag() called - initScriptIndex=" + initScriptIndex);
		this.initScriptIndex = 0;
		log.debug("makeStartTag() reset initScriptIndex - initScriptIndex=" + initScriptIndex);

		if (this.getDateEnd() == null)
			throw new SystemException("Calendar tag has a null end date");

		if (this.getDateStart() == null)
			throw new SystemException("Calendar tag has a null start date");
		
		Div mainDiv = createMainDiv();
		StringBuffer jscriptMethodContent = new StringBuffer();
		List people = this.getPeople();
	
		int personWidth = (maximumWidth - timeMarginWidth) / people.size();
		if (personWidth < minimumPersonWidth) {
			personWidth = minimumPersonWidth;
		} else if (personWidth > maximumPersonWidth) {
			personWidth = maximumPersonWidth;
		} 
		long headerTop = 0;
		long headerHeight = 30;

		long calendarTop = headerTop + headerHeight;
		long calendarLeft = 0;
		long calendarWidth = timeMarginWidth + personWidth * people.size();
		long detailLeft = 5;
		long detailHeight = 260;
		long detailWidth = 250;
		long detailTop = calendarTop;
		if (this.getDetailAlign().equals(DETAIL_ALIGN_TOP)) {
			
		}
		
		
		Div detailDiv = this.createDetailDiv(detailLeft, detailTop, detailWidth, detailHeight);
		long personDivHeight = ((60 * 24) / minutesPerPixel);
		Div calendarDiv = createCalendarDiv(calendarLeft, calendarTop, calendarWidth, personDivHeight + 30);

		
		if (this.getTimeMode().equals(CalendarTag.TIME_MODE_DAY)) {
			displayDayGrid(calendarDiv);
		} else {
			throw new NotSupportedException("Do not know how to draw grid for: " + this.getTimeMode());
		}
		
		long personLeft = timeMarginWidth;
		Iterator iter = people.iterator();
		while (iter.hasNext()) {
			Person person = (Person)iter.next();
			long personHeaderLeft = calendarLeft + personLeft;
			
			Div personHeaderDiv = new Div();
			personHeaderDiv.setStyle(
					  "position: absolute; top: " + headerTop + "px;"
					+ "left: " + personHeaderLeft+ "px;"
					+ "width: " + personWidth + "px;" 
					+ "height: " + headerHeight + ";"
					+ "background-color:" + personBackgroundColor +	";" 
					+ "text-align: left;" 
					+ "padding: 0px;" 
					+ "border: solid 1px #339999;"
					+ "z-index:3;");
			mainDiv.addElement(personHeaderDiv);
			Font font = new Font();
			font.setClass("stdFont");
			font.addElement(person.getFirstName() + " " + person.getLastName());
			SimpleDateFormat format = CoreServiceLocator.getDateFormat().getDateFormatForDayDateMonthYear(this.getLocale());
			String dateStr = format.format(this.getDateStart());
			font.addElement("<br>" + dateStr);
			personHeaderDiv.addElement(font);
			
			log.debug("personDivHeight = " + personDivHeight);
			long personDivTop = 0;
			Div personDiv = new Div();
			personDiv.setStyle(
				  "position:absolute; top:" + personDivTop + "px;"
				+ "left: " + personLeft+ "px;"
				+ "width: " + personWidth + "px;" 
				+ "height:" + personDivHeight + ";"
				+ "background-color: " + personBackgroundColor +	";" 
				+ "text-align: left;" 
				+ "padding: 0px;" 
				+ "z-index: 0;"
				+ "overflow:none;");
			calendarDiv.addElement(personDiv);
			
			PersonExpirableDisplayAttributes displayInfo = new PersonExpirableDisplayAttributes();
			
			List expirables = this.findPersonExpirables(person, this.getDateStart(), this.getDateEnd());
			CalendarItemsDisplayInfo displayInfoForItems =  new CalendarItemsDisplayInfo(expirables);
			allPeoplesExpirables.addAll(expirables);
			Iterator encounterIter = expirables.iterator();
			while (encounterIter.hasNext()) {
				ExpirableRecordObject personExpirable = (ExpirableRecordObject)encounterIter.next();
				displayExpirable(person, displayInfo, personExpirable, jscriptMethodContent, personDiv, displayInfoForItems, personWidth);
			}
			personLeft = personLeft + personHorizSpacing + personWidth;
		}

		Script script = createJavaScript(jscriptMethodContent.toString(), allPeoplesExpirables);
		
		mainDiv.addElement(calendarDiv);
		//mainDiv.addElement(detailDiv);
		
		Table table = new Table();
		table.setWidth("100%");
		Row row = table.addRow();
		
		Cell calendarCell = row.addCell();
		calendarCell.setVAlign("top");
		
		Cell detailCell = row.addCell();
		detailCell.setVAlign("top");

		calendarCell.addElement(mainDiv);
		detailCell.addElement(detailDiv);
		
		String html = "\r\n"+ script.toString() + "\r\n" + table.toString();
		return html;
	}

	
	private void displayExpirable(Person calendarPerson, PersonExpirableDisplayAttributes displayInfo, 
			ExpirableRecordObject personExpirable, StringBuffer jscriptMethodContent, 
			Div personDiv, CalendarItemsDisplayInfo itemsDisplayInfo, long personWidth) {
		CalendarItemDisplayInfo itemDisplayInfo = itemsDisplayInfo.findItemDisplayInfo(personExpirable);
		displayInfo.setBackgroundColor(this.meetingBackgroundColor);
		displayInfo.setTitle(null);
		displayInfo.setSubTitle(null);
		displayInfo.setIconUrl(null);
		displayInfo.setContent(null);
		
		long millisEllapsed = personExpirable.getDateEnd().getTime() - personExpirable.getDateStart().getTime();
		long minutesEllapsed = millisEllapsed / (60 * 1000);
		
		long minutesIntoDay = DateUtil.getMinutesIntoDay(personExpirable.getDateStart());
		long top = (minutesIntoDay / minutesPerPixel);
		long height = minutesEllapsed / minutesPerPixel;
		if (log.isDebugEnabled()) {
			log.debug("Expirable display: " 
					+ "\r\n - Start: " + personExpirable.getDateStart()
					+ "\r\n - End:   " + personExpirable.getDateEnd()
					+ "\r\n - Mins into day:   " + minutesIntoDay
					+ "\r\n - Mins ellapsed:   " + minutesEllapsed
					+ "\r\n - Top:             " + top
					+ "\r\n - Height:          " + height
					);
		}
	
		try {
			this.findDisplayAttributes(calendarPerson, personExpirable, displayInfo);
		} catch (Exception ex) {
			log.error("Exception finding person display attributes from derived class", ex);
		}
		String bgColor = this.meetingBackgroundColor;
		if (displayInfo.getBackgroundColor() != null) {
			bgColor = displayInfo.getBackgroundColor();
		}

		long laneWidth = personWidth / itemsDisplayInfo.getNumberOfRequiredLanes();
		int laneNumber = itemDisplayInfo.getLaneNumber();
		long itemLeft = laneNumber * laneWidth;

		long itemWidth = laneWidth;
		if (itemsDisplayInfo.canItemSpanAcrossAllLanes(itemDisplayInfo)) {
			itemWidth = personWidth;
		}
		Div personEncounterDiv = createPersonEncounterDiv(itemLeft, top, itemWidth, height, bgColor);
		
		String uniqueId = this.addDivDetailLoadScript(jscriptMethodContent, personEncounterDiv, personExpirable);
		
		
		String onShowDetailParam ="showCalendarItemDetails('" + uniqueId + "')";
		personEncounterDiv.setOnMouseOver(onShowDetailParam);

		String onDeactivateDetailDisplay ="deactivateDetailDisplay()";
		personEncounterDiv.setOnMouseOut(onDeactivateDetailDisplay);

		String onMouseClickItem ="onCalendarItemClickDisplay('"
				+ uniqueId + "')";
		personEncounterDiv.setOnClick(onMouseClickItem);
				
		if (displayInfo.getTitle() != null)	{				
			personEncounterDiv.addElement(TextHelper.getFont("subFont", "<b>" 
					+ displayInfo.getTitle() + "</b>"));
		}
		if (displayInfo.getSubTitle() != null) {
			personEncounterDiv.addElement(TextHelper.getFont("subFont", "<br><i>" 
					+ displayInfo.getSubTitle() + "</i>"));
		}
		
		if (displayInfo.getContent() != null) {
			personEncounterDiv.addElement(TextHelper.getFont("subFont", "<br>" + displayInfo.getContent() 
					+ ""));
		}
		personDiv.addElement(personEncounterDiv);
	}

	private String createJSMethod_findDetailNode() {
		StringBuffer methodStr = new StringBuffer();
		methodStr.append("function findDetailNode(detailId) {\r\n");
		methodStr.append("	if (currentCalendarNodes == null) {\r\n");
		methodStr.append("	    initializeCalendarData();\r\n");
		methodStr.append("	}\r\n");
		methodStr.append("	for (i = 0; i < currentCalendarNodes.length; i++) {\r\n");
		methodStr.append("		node = currentCalendarNodes[i];\r\n");
		methodStr.append("		nodeId = node.getAttribute('id');\r\n");
		methodStr.append("		if (detailId == nodeId) {\r\n");
		methodStr.append("			return node; \r\n");
		methodStr.append("		}\r\n");
		methodStr.append("	}\r\n");
		methodStr.append("}\r\n");
		return(methodStr.toString());
	}

	
	private Div createMainDiv() {
		Div mainDiv = new Div();
		mainDiv.setStyle("position:relative; " +
			"top:0px; " +
			"left:0px; " +
			"width:100%; " +
			"height:400px;"
			+ "background-color:" + this.backgroundColor + ";" 
			+ "border: solid 1px #440000;"
			+ "text-align:left;"
			+ "padding:0px;"
			+ "overflow:auto;"
			//+ "scrollbar-face-color: #606060;" 
			//+ "scrollbar-highlight-color: #707000;"
			//+ "scrollbar-3dlight-color: #a0a0a0;"
			//+ "scrollbar-shadow-color : #909000;"
			//+ "scrollbar-darkshadow-color: #333333;"
			//+ "scrollbar-track-color: #606050;"
			//+ "scrollbar-arrow-color: #d0d0a0;"
			);
		mainDiv.setPrettyPrint(true);
		return (mainDiv);
	}

	
	private Script createJavaScript(String jscriptMethodContent, Collection allPeoplesExpirables) {
		log.debug("makeStartTag() - making javascript code to place on form");
		Script script = new Script();
		script.setLanguage("javascript");
		script.addElement("var currentCalendarNodes;\r\n");
		script.addElement("var calendarItems = new Array();\r\n");
		script.addElement("var loadedCalendarItems = false;\r\n");
		script.addElement(this.createJScript_ShowCalendarItemDetailsMethod());
		script.addElement("\r\n\r\n");
		script.addElement(this.createJScript_DeactivateDetailDisplay());
		script.addElement("\r\n\r\n");
		script.addElement(this.createJScript_OnItemClick());
		script.addElement("\r\n\r\n");
		//script.addElement(this.createJScript_OnCalendarClick());
		//script.addElement("\r\n\r\n");
		script.addElement(this.createJSMethod_findDetailNode());
		script.addElement("\r\n\r\n");
		script.addElement(this.createJScript_InitialDataLoaderMethod(allPeoplesExpirables));
		script.addElement("\r\n\r\n");
		return(script);
	}
	
	protected void displayDayGrid(Div calendarDiv) {
		String amPmStr = "am";
		long gridTop = 0;
		for (int hour = 0; hour < 24; hour++) {
			long lineY = gridTop + ((60 * hour) / minutesPerPixel);
			if (hour >= 12) {
				amPmStr = "pm";
			}
			Div timeLine = new Div();
			timeLine.setStyle("position:absolute;" +
					"top: " + lineY + "px;" +
					"left: 0px;" +
					"width: 100%;" +
					"background-color: " + gridLineColor +	";" + 
					"height: 1px;" +
					"z-index: 1;");
			calendarDiv.addElement(timeLine);
			IMG img = new IMG();
			img.setWidth("100%");
			img.setHeight(1);
			img.setSrc("/hrs-intranet/graphics/cleardot.gif");
			img.setAlt("");
			timeLine.addElement(img);
	
			Div timeLineCaptionDiv = new Div();
			timeLineCaptionDiv.setStyle("position:absolute;" +
					"top:" + (lineY + 2) + "px;" +
					"left:0px;" +
					"width:100px;" +
					"height:12px;" +
					"z-index:1;");
			String timeCaption = "" + hour + ":00 " + amPmStr;
			timeLineCaptionDiv.addElement(TextHelper.getFont("calendarTimeFont", timeCaption));
			calendarDiv.addElement(timeLineCaptionDiv);
		}
	}	


	public String getPersonId() {
		return personId;
	}
	
	public void setPersonId(String personId) {
		this.personId = personId;
	}
	
	public String getTimeMode() {
		return timeMode;
	}
	
	public void setTimeMode(String timeMode) {
		this.timeMode = timeMode;
	}

	
	private String addDivDetailLoadScript(StringBuffer scriptBody, Div div, ExpirableRecordObject expirable) {
		
		String uniqueId = CalendarTag.getUniqueId(expirable);  
		String divId = "div_" + uniqueId;
		log.debug("addDivDetailLoadScript() - id = " + uniqueId  + "  Div ID: " + divId);
		div.setID(divId);
		scriptBody.append("   calendarItems[" + uniqueId + 
				"] = new CalendarItem(" + divId 
				+ ", \"Title " 
						 + uniqueId + "\", \"Subtitle " +
						 uniqueId +	"\", \"Description of calendar item " +
						 uniqueId +	"\");\r\n");
		
		this.initScriptIndex++; 
		return(uniqueId);
	}
	
	private final String createJScript_InitialDataLoaderMethod(Collection expirables) {
		StringBuffer methodCode = new StringBuffer();
		try {
			methodCode.append(" function initializeCalendarData() { \r\n");
			Document dataDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
			Element rootElement = dataDocument.createElement("personExpirables");
			dataDocument.appendChild(rootElement);
			
			Iterator iter = expirables.iterator();
			while (iter.hasNext()) {
				ExpirableRecordObject expirable = (ExpirableRecordObject)iter.next();
				Element expirableElement = dataDocument.createElement("personExpirable");
				rootElement.appendChild(expirableElement);
				this.personExpirableToXml(expirable, expirableElement, this.getLocale());
			}
			boolean prettyPrint = false;
			String dataStr = DomUtils.elementToXml(rootElement, prettyPrint);
		    methodCode.append("    log('Loading initial data into XML element array');\r\n");
			methodCode.append("    log('XML string represent data for calendar items'); \r\n");
			methodCode.append("    xmlStr = '" + dataStr + "'; \r\n");
			methodCode.append("    log('Parse data string into xml document'); \r\n");
			methodCode.append("    xmlDoc = AjxXmlDoc.createFromXml(xmlStr); \r\n");
		    methodCode.append("    log('getting DOM document');\r\n");
			methodCode.append("    nativeDocument = xmlDoc.getDoc(); \r\n");
		    methodCode.append("    log('finding calendar items and storing in global array');\r\n");
			methodCode.append("    currentCalendarNodes = nativeDocument.getElementsByTagName(\"personExpirable\"); \r\n");
		    methodCode.append("    log('finished loading initial data');\r\n");
			methodCode.append(" } \r\n");
		} catch (Exception ex) {
			throw new SystemException("Exception making xml data loading method.", ex);
		}
		return(methodCode.toString());
	}

	private Div createDetailDiv(long left, long top, long width, long height) {
		Div detailDiv = new Div();
		detailDiv.setID("detailDiv");
		detailDiv.setStyle("cursor: pointer;position:relative; " +
			"top: " + top + "px; " +
			"left: " + left + "px; " +
			"width: " + width + "px; " +
			"height: " + height + "px;"
			+ "background-color: " + personBackgroundColor +	";" 
			+ "border: solid 1px #440000;"
			+ "text-align: left;"
			+ "padding: 0px;"
			+ "overflow: auto;"
//			+ "scrollbar-face-color: #606060;" 
//			+ "scrollbar-highlight-color: #707000;"
//			+ "scrollbar-3dlight-color: #a0a0a0;"
//			+ "scrollbar-shadow-color : #909000;"
//			+ "scrollbar-darkshadow-color: #333333;"
//			+ "scrollbar-track-color: #606050;"
//			+ "scrollbar-arrow-color: #d0d0a0;"
			);
		detailDiv.addElement("<font>Calendar Detail<br><i>Pass mouse over calendar item to see the item's details.</i></font>");
		return(detailDiv);
	}	

	
	private Div createCalendarDiv(long left, long top, long width, long height) {
		Div calendarDiv = new Div();
		calendarDiv.setStyle("position:relative; " +
			"top: " + top + "px; " +
			"left: 0px; " +
			"width:" + width + "px; " +
			"height:" + height + "px;"
			+ "background-color:" + this.backgroundColor + ";" 
			+ "border: solid 1px #440000;"
			+ "text-align:left;"
			+ "padding:0px;"
			+ "overflow:auto;"
			//+ "scrollbar-face-color: #606060;" 
			//+ "scrollbar-highlight-color: #707000;"
			//+ "scrollbar-3dlight-color: #a0a0a0;"
			//+ "scrollbar-shadow-color : #909000;"
			//+ "scrollbar-darkshadow-color: #333333;"
			//+ "scrollbar-track-color: #606050;"
			//+ "scrollbar-arrow-color: #d0d0a0;"
			);
		
		//String onClick = "onCalendarClick()"; 
		//calendarDiv.setOnClick(onClick);
		return(calendarDiv);
	}

	private Div createPersonEncounterDiv(long left, long top, long width, long height, String bgColor) {
		Div personEncounterDiv = new Div();
		
		log.debug("CreatePersonEncounterDiv() "
				+ "\r\n - Height = " + height);
		personEncounterDiv.setStyle(				  
				"position:absolute; " 
				+ "top:" + top + "px;"
				+ "left:" + left+ "px;"
				+ "width:" + width + ";" 
				+ "height:" + height + "px;"
				+ "background-color:" + bgColor +	";" 
				+ "text-align:left;" 
				+ "padding:0px;" 
				+ "border: solid 1px #339999;"
				+ "z-index:0;"
				+ "overflow:auto;"
				);
		return(personEncounterDiv);
	}

	/**
	 * Get unique id for encounter for reference to XML element.
	 * @param encounter
	 * @return
	 */
	public static String getUniqueId(ExpirableRecordObject encounter) {
		String uniqueId = encounter.getClass().getSimpleName() + encounter.getId();
		return(uniqueId);
	}
	
	public String getDetailAlign() {
		return detailAlign;
	}
	
	public void setDetailAlign(String detailAlign) {
		this.detailAlign = detailAlign;
	}

}
